package cn.neo.hhrpc.core.provider;

import cn.neo.hhrpc.core.annotation.HHProvider;


import cn.neo.hhrpc.core.config.AppConfigProperties;
import cn.neo.hhrpc.core.config.ProviderConfigProperties;
import cn.neo.hhrpc.core.meta.InstanceMeta;
import cn.neo.hhrpc.core.meta.ProviderMeta;
import cn.neo.hhrpc.core.meta.ServiceMeta;
import cn.neo.hhrpc.core.registry.RegistryCenter;
import cn.neo.hhrpc.core.util.MethodUtils;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.Data;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

import java.lang.reflect.Method;
import java.net.InetAddress;
import java.util.*;

/**
 * 服务提供者启动类
 */
@Data
@Slf4j
public class ProviderBootstrap implements ApplicationContextAware {

    ApplicationContext applicationContext;
    RegistryCenter rc;
    /**
     * 将接口(全限定名)跟具体实现的bean映射起来
     */
    private MultiValueMap<String, ProviderMeta> skeleton = new LinkedMultiValueMap<>();
    private InstanceMeta instance;
    private String port;
    private AppConfigProperties appConfigProperties;
    private ProviderConfigProperties providerConfigProperties;

    // 灰度发布-流量调拨：节点标识
//    @Value("#{${app.metas}}")
//    private Map<String, String> metas;

    public ProviderBootstrap(String port, AppConfigProperties appConfigProperties, ProviderConfigProperties providerConfigProperties) {
        this.port = port;
        this.appConfigProperties = appConfigProperties;
        this.providerConfigProperties = providerConfigProperties;
    }


    // 被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行，并且只会被服务器执行一次。@PostConstruct在构造函数之后执行，init()方法之前执行。
    // 该注解的方法在整个Bean初始化中的执行顺序：
    //  Constructor(构造方法) -> @Autowired(依赖注入) -> @PostConstruct(注释的方法)。
    // 该注解相当于init method,只适应于提供者，对消费者不适应
    // 对应的destroy method销毁方法：@PreDestroy
    @PostConstruct
    public void init() {
        // 获取所有加了该注解的bean
        Map<String, Object> providers = applicationContext.getBeansWithAnnotation(HHProvider.class);
        rc = applicationContext.getBean(RegistryCenter.class);

        // 此处x为bean的名字，非全限定名
        providers.keySet().forEach(System.out::println);
        ;
        providers.values().forEach(this::genInterface);

        // 为什么不在该方法中往zk上注册呢？
        // 此时注册，虽然zk上有，但是spring中还没有完整的初始化bean，服务不可用。所以需要延迟往ZK注册
    }

    /**
     * 往ZK注册，在ApplicationRunner中执行
     */
    @SneakyThrows
    public void start() {
        // 获取ip
        String ip = InetAddress.getLocalHost().getHostAddress();
        // 将灰度信息添加到实例中，这样 consumer 端才能获取到灰度信息.才能知道该服务是否是灰度服务(即了解那些 provider 是新版本，哪些 provider 是旧版本)
        instance = InstanceMeta.http(ip, Integer.valueOf(port)).addParams(providerConfigProperties.getMetas());
        // rc要在服务注册前初始化
        rc.start();
        // 延迟注册
        skeleton.keySet().forEach(this::registerService);
    }


    // 被@PreDestroy修饰的方法会在服务器卸载Servlet的时候运行，并且只会被服务器调用一次，类似于Servlet的destroy()方法。
    // 被@PreDestroy修饰的方法会在destroy()方法之后运行，在Servlet被彻底卸载之前。执行顺序如下所示。
    //  调用destroy()方法->@PreDestroy->destroy()方法->bean销毁。

    @PreDestroy
    public void stop() {
        log.info(" ===> unreg all services");
        skeleton.keySet().forEach(this::unregisterService);
        // rc在服务取消注册后关闭
        rc.stop();
    }

    private void unregisterService(String service) {
        ServiceMeta serviceMeta = ServiceMeta.builder()
                .app(appConfigProperties.getId())
                .namespace(appConfigProperties.getNamespace())
                .env(appConfigProperties.getEnv())
                .name(service)
                .build();
        rc.unregister(serviceMeta, instance);
    }

    private void registerService(String service) {
        ServiceMeta serviceMeta = ServiceMeta.builder()
                .app(appConfigProperties.getId())
                .namespace(appConfigProperties.getNamespace())
                .env(appConfigProperties.getEnv())
                .name(service)
                .build();
        rc.register(serviceMeta, instance);
    }

    private void genInterface(Object impl) {
        // 一个接口有多个实现类或者一个类实现了多个接口
        Arrays.stream(impl.getClass().getInterfaces()).forEach(
                service -> {
                    Arrays.stream(service.getMethods())
                            .filter(method -> !MethodUtils.checkLocalMethod(method))
                            .forEach(method -> {
                                createProvider(service, impl, method);
                            });
                });
    }

    private void createProvider(Class<?> service, Object impl, Method method) {
        ProviderMeta providerMeta = ProviderMeta.builder()
                .method(method)
                .serviceImpl(impl)
                .methodSign(MethodUtils.methodSign(method))
                .build();
        log.info(" create a provider: " + providerMeta);
        skeleton.add(service.getCanonicalName(), providerMeta);
    }


}
